package magnet.processor.instances.aspects.factory;

import com.squareup.javapoet.*;
import magnet.Factory;
import magnet.Scope;
import magnet.Scoping;
import magnet.processor.instances.CreateMethod;
import magnet.processor.instances.FactoryType;
import magnet.processor.instances.MethodParameter;

import javax.lang.model.element.Modifier;

import static magnet.processor.instances.Instances.PARAM_SCOPE_NAME;

public class CustomFactoryCreateMethodGenerator implements CreateMethodGenerator {

    private FactoryType factoryType;
    private TypeName factoryFieldType;
    private MethodSpec.Builder instantiateMethodBuilder;
    private CodeBlock.Builder instantiateMethodCodeBuilder;
    private StringBuilder constructorParametersBuilder = new StringBuilder();

    @Override
    public void visitFactoryClass(FactoryType factoryType) throws IllegalAccessException {
        this.factoryType = factoryType;
        constructorParametersBuilder.setLength(0);
        if (factoryType.getCustomFactoryType() == null) {
            throw new IllegalAccessException("Required value was null.");
        }
        TypeName customFactoryType = factoryType.getCustomFactoryType();
        this.factoryFieldType = customFactoryType;
        if (customFactoryType instanceof ParameterizedTypeName) {
            this.factoryFieldType = ParameterizedTypeName.get(((ParameterizedTypeName) customFactoryType).rawType, factoryType.getInterfaceType());
        }
    }

    @Override
    public void enterCreateMethod(CreateMethod createMethod) {
        this.instantiateMethodBuilder = MethodSpec
                .methodBuilder("instantiate")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(Scope.class, "scope")
                .returns(factoryType.getInterfaceType());

        this.instantiateMethodCodeBuilder = CodeBlock.builder();
    }

    @Override
    public void visitCreateMethodParameter(MethodParameter parameter) {
        CreateMethodGenerator.addCreateParameterStatement(instantiateMethodCodeBuilder, parameter);
        constructorParametersBuilder.append(parameter.getName()).append(", ");
    }

    @Override
    public void exitCreateMethod() {
        if (constructorParametersBuilder != null && constructorParametersBuilder.length() > 0) {
            constructorParametersBuilder.setLength(constructorParametersBuilder.length() - 2);
        }
    }

    @Override
    public void generate(TypeSpec.Builder typeBuilder) {

        instantiateMethodBuilder
                .addCode(instantiateMethodCodeBuilder.build());
        CreateMethodGenerator
                .addNewInstanceStatement(instantiateMethodBuilder,
                        constructorParametersBuilder.toString(),
                        factoryType.getCreateStatement());

        typeBuilder
                .addSuperinterface(
                        ParameterizedTypeName.get(
                                ClassName.get(Factory.Instantiator.class),
                                factoryType.getInterfaceType()
                        )
                )
                .addField(
                        FieldSpec
                                .builder(factoryFieldType, "factory")
                                .addModifiers(Modifier.PRIVATE)
                                .initializer("null")
                                .build()
                )
                .addMethod(
                        MethodSpec
                                .methodBuilder("create")
                                .addAnnotation(Override.class)
                                .addModifiers(Modifier.PUBLIC)
                                .addParameter(Scope.class, PARAM_SCOPE_NAME)
                                .returns(factoryType.getInterfaceType())
                                .addCode(
                                        CodeBlock.builder()
                                                .beginControlFlow("if (factory == null)")
                                                .addStatement("factory = new $T()", factoryFieldType)
                                                .endControlFlow()
                                                .addStatement(
                                                        "return factory.create(scope, $T.class, $S, $T.$L, this)",
                                                        factoryType.getInterfaceType(),
                                                        factoryType.getClassifier(),
                                                        Scoping.class,
                                                        factoryType.getScoping()
                                                )
                                                .build()
                                )
                                .build()
                )
                .addMethod(
                        instantiateMethodBuilder.build()
                );



    }
}
